use std::sync::Arc;

use axum::body::Bytes;
use serde::Serialize;
use sqlx::SqlitePool;
use tokio::sync::{broadcast, Mutex};

use crate::{
    error::AppErr,
    robot::RobotStatus,
    store::{self, new_sqlite_pool},
    utils::{cbor2, messager::Messager, UninitMutCell},
};

const TY_INFO: u8 = 1;

const TY_ROBOT_STATUS: u8 = 2;
const TY_CARGO1_STATUS: u8 = 3;
const TY_CARGO2_STATUS: u8 = 4;
const TY_DROP_STATUS: u8 = 5;
const TY_DOOR_STATUS: u8 = 6;
const TY_HEAT_STATUS: u8 = 7;
const TY_WEIGHT_STATUS: u8 = 8;

const TY_OTA_STATUS: u8 = 9;
const TY_LOG: u8 = 10;

pub struct App {
    robot_status: Mutex<Option<Arc<RobotStatus>>>,
    status_messager: Messager<Bytes>,
    pub pool: SqlitePool,
}

pub static APP: UninitMutCell<App> = UninitMutCell::const_new();

pub async fn app_init(path: &str) -> Result<(), AppErr> {
    let app = App {
        robot_status: Mutex::const_new(None),
        status_messager: Messager::new(),
        pool: new_sqlite_pool(path).await?,
    };
    unsafe {
        APP.write(app);
    };

    store::init().await?;
    Ok(())
}

impl App {
    pub fn subscribe(&self) -> broadcast::Receiver<Bytes> {
        self.status_messager.subscribe()
    }

    pub async fn robot_status(&self) -> Option<Arc<RobotStatus>> {
        self.robot_status.lock().await.clone()
    }

    pub async fn notify_robot_status(&self, status: Arc<RobotStatus>) {
        self.broadcast(TY_ROBOT_STATUS, &status);
        *self.robot_status.lock().await = Some(status);
    }

    pub fn notify_info(&self, msg: &str) {
        self.broadcast(TY_INFO, &msg);
    }

    pub fn notify_ota(&self, msg: &str) {
        self.broadcast(TY_OTA_STATUS, &msg);
    }

    pub fn notify_log(&self, msg: &str) {
        self.broadcast(TY_LOG, &msg);
    }

    pub fn notify_drop<T: Serialize>(&self, status: &T) {
        self.broadcast(TY_DROP_STATUS, status);
    }

    pub fn notify_cargo1<T: Serialize>(&self, status: &T) {
        self.broadcast(TY_CARGO1_STATUS, status);
    }

    pub fn notify_cargo2<T: Serialize>(&self, status: &T) {
        self.broadcast(TY_CARGO2_STATUS, status);
    }

    pub fn notify_heat<T: Serialize>(&self, status: &T) {
        self.broadcast(TY_HEAT_STATUS, status);
    }

    fn broadcast<T: Serialize>(&self, ty: u8, status: &T) {
        let mut buf = Vec::new();
        buf.push(ty);

        if let Ok(_) = cbor2::encode(&status, &mut buf) {
            self.status_messager.notify(buf.into());
        }
    }
}
